package main

import (
	"bytes"
	"crypto/aes"
	"crypto/cipher"
	"crypto/hmac"
	"crypto/rand"
	"crypto/sha256"
	"encoding/base64"
	"encoding/json"
	"fgo/advance/jwt/other/utils"
	"fmt"
	"github.com/techoner/gophp/serialize"
	"io"
	"log"
	"net/url"
	"strings"
)

type Payload struct {
	IV    string `json:"iv"`
	Value string `json:"value"`
	Mac   string `json:"mac"`
}

func laravelDecypt(cookie []byte) []byte {
	cookieBytes, err := base64.StdEncoding.DecodeString(string(cookie))
	if err != nil {
		log.Fatal(err)
	}
	var payload Payload
	if err = json.Unmarshal(cookieBytes, &payload); err != nil {
		log.Fatal(err)
	}
	var (
		key, _ = utils.Base64Decode("+wry7cKWBPI/a9NvIYjNjvzfjadLDIBF6qLrqhA6sHg=")
		iv, _  = utils.Base64Decode(payload.IV)
	)
	encrData, err := base64.StdEncoding.DecodeString(payload.Value)
	if err != nil {
		log.Fatal(err)
	}
	decrptData, err := AesDecrypt(encrData, key, iv)
	if err != nil {
		log.Fatal(err)
	}
	return decrptData
}

//cookie := "eyJpdiI6IlphS08wK2JQdjVsS0RuWmNRaXQwclE9PSIsInZhbHVlIjoicHlBQXZYZ25wMlB1bTFjK3phSzluMHpuTnlUMUZCVEloZW4zbzBzZHF1RkFqdW5JM1M0ZFdKNDBpeXNJb0dUaXdWMHpwY1BHSWd5dTdcL1RsWmYyYjl0bkE4UTh1MmZYMkFZeDlGMHdmXC9uYzN5M0RKcXpOWDZEc1dlOEU5c0hmdit4ZFFGY3JoWEZBSHZMbzBScHcxMHRSeEZWclJ2OGFIR09PUHh1M3I3bmNWR3VoZEVCRjAyK2ZFckRsQW15RWNYTTVxXC95d0dzYVF3cHpPUENsMWxoK0lDRndBWW95YVJ6d0hWa3hSUlNadVpBOWhYb29KNFlDNkZDdGRraU5jUkpCNEhaQjFvZHk3NjRCYitNR2ljYVRBSGlWTjhwRndlbUx5Vkpnd2dQYVlWSk1TNmY2SWFlblViSHlRYUVZRUc4OEJHYXNGaXdpTENwNnhNMzFHaStnMVJjZ1ZheXZxY2o5RmZcL3hKT3dRK0YrMWpSN3JmTUpKXC83eTJGaEMwcnJVSkZkZDlkc25LY1Z5QWFCUU16aEF3UTEwUnRwKzNGblM3UWFWNk9VdWdoWkRyczdQY1FvVWUxZTQwclRaalVJSzJjSUppemxVTENZSXJMMkU4TUx3bjJyVUlod3czQTRHdmRQSmpwY1BVeVZEUlkzUUQwKzI1VGMyS1Q4SFNLWlVrdHlIcXNORkRsUjlQYXk0cFBES2FmUmw2WGRNaFJBaTZDTHhBK2VhVzUzcEVEa1VkMU5kOUtNZ2pyU2hMUzVcL3JkMFdlZ0RTOHRDK0RONEY5c0ZxK2JmcGZ0MXpIcmpEdk5JNGo3bm1xZHlLV2s4cEhYUG1lM28yeVlKY1QyVjVGSWFaUUp0NTR4WHVqQzZ1SW81S0FYXC9PMExrUGQwMmdZcGRsblM4emplVTA5bGVDS0VpajMwakNjeU9ES3g1alZOSHo5dTNzNG5odENkUkVLR2dTMDFGUDRVOXA1dkh1VEtwWnJaZUtsa2x0UDE2OGJKaVgwZGZDdnFkbWJsUFZvb0IxUDZzME9JQnpZTDVaclRuanJWNFZBR1l6cTQ2SjIxYWcycFkwakhKTDcyaDNzWkxZUXNrNkI3eE41Uk1lWFFNMVBVTHZwK3hwOWpheXdTczBuODcwZE45RU9cLzg4Z3BFa2ErclRES3hqbVRLUXJ5Z3NEcUNwck93VXY1UmlRQ1ZZWGZTeE4xNTVRdDlnYzFhazRlWmtudzZiQjdBZlp4XC9IVVJBcm5IeU9IQmEwN3hsOElKK2lKaGdOVWE0UEY2elwvRFRTcFVMYXhVUEtIRUJpbEZtV3MwVDJDS3BYc2E4Q0JVNFY4eUZsZ0V0YU53d0grVXVVbHRNWHpGOHciLCJtYWMiOiI3MGQyODc3YWQwYmFhMDBkMjI1YzUzOWE5ZjAzOWY2NTFiMTY3NmI5ODliOTA4NjIxY2Q3MDFiNWEyYjM0OWVlIn0%3D"
//var token = decyptJwtTokenWithCookie(cookie)
//fmt.Println(token)
func decyptJwtTokenWithCookie(cookie string) (token string) {
	cookie, _ = url.QueryUnescape(cookie)
	cookieByte := []byte(cookie)

	decrypt := laravelDecypt(cookieByte)
	decrptData := laravelDecypt(decrypt)

	data, err := serialize.UnMarshal(decrptData)
	if err != nil {
		panic(err)
	}
	token = data.(string)
	return
}

func encyptJwtToken(token string) (cookie string) {
	serValue, err := serialize.Marshal(token)
	if err != nil {
		log.Fatal(err)
	}
	encypt := laravelEncypt(serValue)
	encyptData := laravelEncypt([]byte(encypt))
	cookie = url.QueryEscape(encyptData)
	return
}

// golang 的 base64 有个坑,s比较短时,不添加 '=='
func fixBase64(s string) (ss string) {
	if !strings.HasSuffix(s, "==") {
		return s + "=="
	}
	return ss
}

func laravelEncypt(rawData []byte) string {
	var (
		key, _ = utils.Base64Decode("+wry7cKWBPI/a9NvIYjNjvzfjadLDIBF6qLrqhA6sHg=")
		//iv  = utils.Base64Decode("PN9v7QLBM5evOF22RaDqlQ==")
	)
	block, err := aes.NewCipher(key)
	if err != nil {
		panic(err)
	}
	blockSize := block.BlockSize()

	var iv = make([]byte, blockSize)
	if _, err := io.ReadFull(rand.Reader, iv); err != nil {
		log.Fatal(err)
	}
	// php serialize
	encypt, err := AesEncrypt(rawData, key, iv)
	if err != nil {
		log.Fatal(err)
	}

	var _iv = fixBase64(base64.RawStdEncoding.EncodeToString(iv))
	var _value = base64.RawStdEncoding.EncodeToString(encypt)

	h := hmac.New(sha256.New, key)
	h.Write([]byte(_iv))
	h.Write([]byte(_value))
	var _mac = fmt.Sprintf("%x", h.Sum(nil))
	var payload Payload = Payload{
		IV:    _iv,
		Value: _value,
		Mac:   _mac,
	}
	jsonBytes, err := json.Marshal(payload)
	if err != nil {
		log.Fatal(err)
	}
	return utils.Base64Encode(jsonBytes)
}

func main() {
	rawCookie := "eyJpdiI6Ilg3SWxjR1FYQXV5a0lFRFJSWE9WVWc9PSIsInZhbHVlIjoiSFwvMDB6akdLZmdmYThpRHRwRE1rY0lCXC9ESUNLeWVEZCtZUlB2dFU5cGQ0T091dDBcL25ISFRzZ1V2SkZONkM2c3NhTEt2MW9RemoyT2RMOFNIOE5NVlo0VUNkb3lSQlV0R0xoOGpNWUFNM0xiVDh5c3V1UWFFYXJTTURHUkFPazNIdkFGQnRyNWFQZ0FkMmE0bGZISjRKRmRMRXVFdWk1WDl4XC9HbU1sNVwvQzd4UnR1QlhCTjhNM2ZKcjBMbm5pT1RKd2tMcU5cL0NLRDZYZWM2QjU0cnFEXC81ekhNTUVCdkFWODhqMlpIdVlicjAxRDM2d2ZNWVwvNjF1bkcrYk9hdCtPcExUYnVFQlZ5YTc4d2loOHRoYnRlcjVWVUp4RkE3eVwvU2RRUVVnOGNCODZFc1VodEpqYytmdXdYakFyVmJzSlZiT3hoK29VdGVuTHQ5aHRpcWVVd01DOEY4eStwd0FtZEVzOWoza1dGWmR6RW1KTGMrVTBudlgxV1hpZHpnSUt2TnVMbG5BTXBjNWZhQmd4TTVuSTVmUU5WcG5xMkVVRkxOc1pGZHRhZGxsXC92MmxNOXc1TWNtdzZGdm9DTGZZUHRKRit1UWFWaUVjMmlVNUF3RGpyWTZoRWswSVU5aEk1aVJ4R2dselVQaitpVGh4Q3Q3S3hHcFYwZ2NWZWk0Wm1aQXlQM254RjlZT0Z2Y2NSbWNYSUl0YjdkR1VYa3pzOFRnbFNKT2paWEI3OXBWeWd1UWZBNFFkcElVYkFsZ3U3clwvZGxPaGlicHlFQTBcL21PUjhudGJ3aUozRGdDN21pNlNldTA0ZFBWcm45M1RNeU9yaWZxOHlyN2lQV0xzeVFLcUZqSlgwODVNUkFhWldNWjNuYVl4aVl1c1hObmVBM2JIM0NWUlJFMW9DMjFTRG1jKzRDa0lKRzNGR0dzM1pvcUdsSjQxNDdjbzRjSjFFTCtcLzVPQkN5ZHVYZnp5aVlrVmtrSXNFeXJndmcwdlNqcjhMWXJYVERJK0hrM3FzZXF0T1JcL3lBdGtCTU1ENUVxRmRuMnh6cXBEdmFva2EwbmlPRTY2OGlFVlhMenpvaTlPOHllS2dYSHpTT3hlRE5hTlozSHN4c1VxT2tSSGNFdXRwS0w3UXN0VE5kaUgxaVhsS0FRb28rN2x6SWlMR3RldmZsV3RjRksyQ0FZWGZHN0pwdEpFYUZZUjk5ZmhMcUxCMStDWEVhK0ZxZnQ4NVBHNis0NXZ5a05ibGY1MXFOXC9SbVhwZkw5QTNYN0thUmdlb2dZMFwvNjk4cXNKTm1ZdlwvV1VLWHZcL0pYS2JcL1BuSGhWWUtpemdHOUJ1dUFIMVZ4dkJvYW1PeFRYQXdMck51QUVvSWIiLCJtYWMiOiI5ZGFjYWJmMDI2YTNhNzczYjI5YzQxOGYxMTMzZjk4YTRiYzk3MzY4MmM4NmMyMjFiNDg0YjExM2I1OTNiMmIxIn0%3D"
	var token = decyptJwtTokenWithCookie(rawCookie)
	token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwOlwvXC8xMjcuMC4wLjE6ODAwMFwvdjJcL2xvZ2luIiwiaWF0IjoxNTcxNTQzMDU4LCJleHAiOjE1NzQxMzUwNTgsIm5iZiI6MTU3MTU0MzA1OCwianRpIjoiTHVpR3BuUEVLVFZsVzZSUCIsInN1YiI6MSwicHJ2IjoiMjNiZDVjODk0OWY2MDBhZGIzOWU3MDFjNDAwODcyZGI3YTU5NzZmNyJ9.nupyYffVuUmdMYS5s4THer2QrgILROCJSOGtlNrdXBQ"
	var cookie = encyptJwtToken(token)
	log.Println(cookie)
}

/*CBC加密 按照golang标准库的例子代码
不过里面没有填充的部分,所以补上
*/
//使用PKCS7进行填充，IOS也是7
func PKCS7Padding(ciphertext []byte, blockSize int) []byte {
	padding := blockSize - len(ciphertext)%blockSize
	padtext := bytes.Repeat([]byte{byte(padding)}, padding)
	return append(ciphertext, padtext...)
}

func PKCS7UnPadding(origData []byte) []byte {
	length := len(origData)
	unpadding := int(origData[length-1])
	return origData[:(length - unpadding)]
}

// AesDecrypt ase 解密
// rawData: base64Decode 之后的数据
func AesDecrypt(rawData, key, iv []byte) (result []byte, err error) {
	combineData := bytesCombine(iv, rawData)
	return aesCBCDecrypt(combineData, key)
}

// AesEncrypt ase 加密
// aes加密，填充秘钥key的16位，24,32分别对应AES-128, AES-192, or AES-256.
func AesEncrypt(rawData, key []byte, iv []byte) (cipherText []byte, err error) {
	var block cipher.Block
	if block, err = aes.NewCipher(key); err != nil {
		return
	}
	//填充原文
	blockSize := block.BlockSize()
	if len(iv) != blockSize {
		err = fmt.Errorf("len(iv) != blockSize(len:%d)", blockSize)
		return
	}
	rawData = PKCS7Padding(rawData, blockSize)
	//初始向量IV必须是唯一，但不需要保密
	cipherText = make([]byte, blockSize+len(rawData))
	//golang 默认前 blockSize 字节大小(16),为 iv ,但是这里我们使用自己生成的 iv
	//iv := cipherText[:blockSize]
	// 生成随机 iv 值
	//if _, err = io.ReadFull(rand.Reader, iv); err != nil {
	//	return
	//}
	//block大小和初始向量大小一定要一致
	mode := cipher.NewCBCEncrypter(block, iv)
	mode.CryptBlocks(cipherText[blockSize:], rawData)

	cipherText = cipherText[blockSize:]
	return
}

// ase 解密
func aesCBCDecrypt(encryptData, key []byte) ([]byte, error) {
	block, err := aes.NewCipher(key)
	if err != nil {
		panic(err)
	}

	blockSize := block.BlockSize()

	if len(encryptData) < blockSize {
		panic("ciphertext too short")
	}
	iv := encryptData[:blockSize]
	encryptData = encryptData[blockSize:]

	// CBC mode always works in whole blocks.
	if len(encryptData)%blockSize != 0 {
		panic("ciphertext is not a multiple of the block size")
	}

	mode := cipher.NewCBCDecrypter(block, iv)

	// CryptBlocks can work in-place if the two arguments are the same.
	mode.CryptBlocks(encryptData, encryptData)
	//解填充
	encryptData = PKCS7UnPadding(encryptData)
	return encryptData, nil
}

func bytesCombine(pBytes ...[]byte) []byte {
	length := len(pBytes)
	s := make([][]byte, length)
	for index := 0; index < length; index++ {
		s[index] = pBytes[index]
	}
	sep := []byte("")
	return bytes.Join(s, sep)
}
